#include <os/account.h>
#include <os/account_group.h>
#include <os/kernelif.h>
#include <os/sconfig.h>
#include <os/memcache.h>
#include <os/safety.h>
#include <os/debug.h>
#include <lib/list.h>
#include <lib/type.h>
#include <lib/string.h>
#include <lib/unistd.h>
#include <lib/errno.h>
#include <sys/fcntl.h>

account_group_t account_group[GROUP_MAX];

static DEFINE_MUTEX_LOCK(account_group_lock);
static int next_id = 0;
static int fd = -1; // global group fd

static int AccountGroupReadConfig();
static int AccountGroupLoad();
static int AccountGroupBuildStr(account_group_t *group, char *line);

void AccountGroupManagerInit()
{
    account_group_t *group;
    int i;

    // init group data
    for (i = 0; i < GROUP_MAX; i++)
    {
        group = &account_group[i];
        group->count = 0;
        group->gid = -1;
        group->attr = 0;
        memset(group->group_name, 0, GROUP_NAME_LEN);
        memset(group->user, -1, GROUP_USER_NUM); // init all user id to -1
        MutexlockInit(&group->lock);
    }
    // load group data
    if (AccountGroupReadConfig() < 0)
    {
        KPrint("[group] read config failed!\n");
        return;
    }
    KPrint("[group] account group init ok!\n");
}

account_group_t *AccountGroupAlloc()
{
    int i;
    MutexlockLock(&account_group_lock, MUTEX_LOCK_MODE_BLOCK);
    for (i = 0; i < GROUP_MAX; i++)
    {
        if (!account_group[i].flags)
        {
            account_group[i].flags = 1;
            account_group[i].gid = next_id++;
            MutexlockUnlock(&account_group_lock);
            return (account_group_t *)&account_group[i];
        }
    }
    MutexlockUnlock(&account_group_lock);
    return NULL;
}

void AccountGroupFree(account_group_t *group)
{
    MutexlockLock(&account_group_lock, MUTEX_LOCK_MODE_BLOCK);
    group->flags = 0;
    group->gid = -1;
    MutexlockUnlock(&account_group_lock);
}

account_group_t *AccountGroupFindByName(char *name)
{
    if (!name)
        return NULL;

    account_group_t *group;
    int i;

    MutexlockLock(&account_group_lock, MUTEX_LOCK_MODE_BLOCK);
    for (i = 0; i < GROUP_MAX; i++)
    {
        group = &account_group[i];
        if (!strcmp(name, group->group_name))
        {
            MutexlockUnlock(&account_group_lock);
            return group;
        }
    }
    MutexlockUnlock(&account_group_lock);
    return NULL;
}

int AccountGroupPush(uint32_t attr, char *name)
{
    if (!name)
        return -1;

    account_group_t *group = AccountGroupAlloc();
    if (!group)
    {
        KPrint("[group] no free group for alloc!\n");
        return -1;
    }

    MutexlockLock(&group->lock, MUTEX_LOCK_MODE_BLOCK);
    group->attr = attr;
    memcpy(group->group_name, name, min(GROUP_NAME_LEN, strlen(name)));
    group->group_name[GROUP_NAME_LEN] = 0;
    group->gid = next_id++;
    MutexlockUnlock(&group->lock);
    KPrint("[group] group name %s attr %x push\n", group->group_name, group->attr);
    return 0;
}

int AccountGroupPop(char *name)
{
    if (!name)
        return -1;

    account_group_t *group = AccountGroupFindByName(name);
    if (!group)
    {
        KPrint("[group] no find group!\n");
        return -1;
    }
    AccountGroupFree(group);
    return 0;
}

int AccountGroupNameCheck(char *name)
{
    char *p = name;
    while (*p)
    {
        // group name too long
        if (!strlen(name) >= GROUP_NAME_LEN)
            return 0;
        if (!GROUP_NAME_VALID(*p))
            return 0;
        p++;
    }
    return 1;
}

int AccountGroupRegister(uint32_t attr, char *name)
{
    if (!name)
        return -1;

    account_group_t *group = AccountGroupFindByName(name);
    if (!group) // no present
    {
        if (!AccountGroupNameCheck(name))
        {
            KPrint("[group] group name %s invalid!\n", name);
            return -1;
        }

        // register group
        AccountGroupPush(attr, name);

        // sync data
        if (AccountGroupSync() < 0)
        {
            KPrint("[group] group sync failed!\n");
            AccountPop(name);
            return -1;
        }
        return 0;
    }
    else
    {
        KPrint("[group] group %s has present!\n", group);
        return -1;
    }
    return 0;
}

int AccountGroupUnRegister(char *name)
{
    if (!name)
        return -1;

    account_group_t *group = AccountGroupFindByName(name);
    if (!group) // group no present
    {
        KPrint("[group] no found group %s!\n", group);
        return -1;
    }

    account_group_t group_backup = *group;
    // remove group
    if (AccountGroupPop(name) < 0)
    {
        KPrint("[group] group remove failed!\n");
        return -1;
    }

    // sync group
    if (AccountGroupSync() < 0)
    {
        KPrint("[group] group sync failed!\n");
        AccountGroupPush(group_backup.attr, name);
        return -1;
    }
    return 0;
}

int AccountGroupSync()
{
    int i;
    char line[GROUP_CONFIG_LEN];
    account_group_t *group = NULL;
    char file[128];

    strcpy(file, ACCOUNT_DIR_PATH);
    strcat(file, "/");
    strcat(file, GROUP_FILE_NAME);
    fd = KFileOpen(file, O_RDWR);
    if (fd < 0)
    {
        KPrint("[group] open file failed!\n");
        return -1;
    }

    // write number of groups
    KFileWrite(fd, &next_id, sizeof(next_id));

    // sync group data to file
    for (i = 0; i < GROUP_MAX; i++)
    {
        group = &account_group[i];
        if (group->flags) // group in use
        {
            AccountGroupBuildStr(group, line);
            KFileWrite(fd, line, GROUP_CONFIG_LEN);
        }
    }
    KFileClose(fd);
    return 0;
}

static int AccountGroupBuildStr(account_group_t *group, char *line)
{
    char attr_str[GROUP_ATTR_BUFF_LEN];
    char user_str[GROUP_USER_BUFF_LEN];

    // make attr string
    if (group->attr == GROUP_ATTR_ADMIN)
    {
        attr_str[0] = 'A';
    }
    else
    {
        if (group->attr == GROUP_ATTR_NORMAL)
            attr_str[0] = 'N';
        else
        {
            if (group->attr == GROUP_ATTR_GUESS)
                attr_str[0] = 'G';
        }
    }
    // make user string
    memcpy(user_str, group->user, sizeof(uid_t) * GROUP_USER_NUM);

    SConfigWrite(line, group->group_name);
    SConfigWrite(line, attr_str);
    SConfigWrite(line, user_str);
    SConfigWriteLine(line);
}

static int AccountGroupReadConfig()
{
    char file[32];
    uint8_t file_exist = 1;

    memset(file, 0, 32);
    strcpy(file, ACCOUNT_DIR_PATH);
    strcat(file, "/");
    strcat(file, GROUP_FILE_NAME);
    if (KFileAccess(file, F_OK) < 0)
    {
        KPrint("[group] create new account group!\n");
        fd = KFileOpen(file, O_CREATE | O_RDWR); // create new
        if (fd < 0)
        {
            KPrint("[group] create new group file %s failed!\n", file);
            return -1;
        }
        KFileClose(fd);
        file_exist = 0;
    }

    if (!file_exist) // no exist
    {
        AccountGroupPush(GROUP_ATTR_ADMIN, "RootAdmin"); // admin group(manager all resource)
        AccountGroupPush(GROUP_ATTR_NORMAL, "User");     // standard group(manager area resource)
        AccountGroupPush(GROUP_ATTR_GUESS, "Guess");     // guess group

        AccountGroupAddUser(ROOT_ACCOUNT_NAME, GROUP_ATTR_ADMIN); // add root user to group

        if (AccountGroupSync() < 0)
        {
            KPrint("[group] sync data failed!\n");
            AccountGroupPop("RootAdmin");
            AccountGroupPop("User");
            AccountGroupPop("Guess");
            return -1;
        }
        return 0;
    }
    else
    {
        if (AccountGroupLoad(file) < 0)
        {
            KPrint("[group] load data from file %s failed!\n");
            return -1;
        }
    }
    return 0;
}

static int AccountGroupLoad(char *file)
{
    if (!file)
        return -1;

    fd = KFileOpen(file, O_RDONLY); // open file
    if (!fd)
    {
        KPrint("[group] open group file %s failed!\n", file);
        return -1;
    }

    // read number of group to used to group ID
    KFileRead(fd, &next_id, sizeof(next_id));

    int size = KFileLSeek(fd, 0, SEEK_END);
    if (size < 0)
    {
        KPrint("[group] group file size error!\n");
        return -1;
    }

    uint8_t *buff = KMemAlloc(size);
    if (!buff)
    {
        KPrint("[group] mem no enough!\n");
        return -1;
    }
    // read file to buffer
    KFileLSeek(fd, 0, SEEK_SET);
    if (KFileRead(fd, buff, size) < 0)
    {
        KPrint("[group] read group file failed!\n");
        return -1;
    }
    KFileClose(fd);

    uint8_t *p = buff;
    char line[GROUP_CONFIG_LEN];

    // parse group data
    while (p)
    {
        p = SConfigReadLine(p, line, GROUP_CONFIG_LEN);
        if (!p)
            break;
        if (AccountGroupScanLine(line) < 0)
            return -1;
    }
    return 0;
}

int AccountGroupScanLine(char *line)
{
    char *p = line;
    char name_buff[GROUP_NAME_BUFF_LEN], attr_buff[GROUP_ATTR_BUFF_LEN];
    int attr;
    uid_t user[GROUP_USER_NUM];

    // read name
    p = SConfigRead(p, name_buff, GROUP_NAME_BUFF_LEN);
    SConfigTrim(name_buff);

    // read attr
    p = SConfigRead(p, attr_buff, GROUP_ATTR_BUFF_LEN);
    SConfigTrim(attr_buff);
    if (attr_buff[0] == 'A')
        attr = GROUP_ATTR_ADMIN;
    else
    {
        if (attr_buff[0] == 'N')
            attr = GROUP_ATTR_NORMAL;
        else
        {
            if (attr_buff[0] == 'G')
                attr = GROUP_ATTR_GUESS;
        }
    }

    // read user
    p = SConfigRead(p, (void *)user, sizeof(uid_t) * GROUP_USER_NUM);

    if (AccountGroupPush(attr, name_buff) < 0)
    {
        KPrint("[group] create group %s failed!\n");
        return -1;
    }

    account_group_t *group = AccountGroupFindByName(name_buff);
    if (!group)
        return -1;
    // copy user data
    memcpy(group->user, user, sizeof(uid_t) * GROUP_USER_NUM);
    return 0;
}

static int AccountGroupFindFreeUser(account_group_t *group)
{
    int i;

    for (i = 0; i < GROUP_USER_NUM; i++)
    {
        if (group->user[i] == -1)
            return i;
    }
    return -1;
}

int AccountGroupAddUser(char *user, char *name)
{
    if (!user || !name)
        return -1;

    account_group_t *group = AccountGroupFindByName(name);
    if (!group)
    {
        KPrint("[group] no found group!\n");
        return -1;
    }
    account_t *account = AccountFindByName(user);
    if (!account)
    {
        KPrint("[group] no found user!\n");
        return -1;
    }

    // bind group
    MutexlockLock(&group->lock, MUTEX_LOCK_MODE_BLOCK);
    account->gid = group->gid;
    int slot = AccountGroupFindFreeUser(group);
    if (slot < 0)
    {
        KPrint("[group] group %s reached to user number limit!\n");
        return -1;
    }
    group->user[slot] = account->uid;
    group->count++;
    MutexlockUnlock(&group->lock);
    KPrint("[group] add user %s to group %s\n", user, group->group_name);
    return 0;
}

int AccountGroupDelUser(char *user, char *name)
{
    if (!user || !name)
        return -1;

    account_group_t *group = AccountGroupFindByName(name);
    if (!group)
    {
        KPrint("[group] no found group!\n");
        return -1;
    }
    account_t *account = AccountFindByName(user);
    if (!account)
    {
        KPrint("[group] no found user!\n");
        return -1;
    }

    MutexlockLock(&group->lock, MUTEX_LOCK_MODE_BLOCK);
    // clear bind group
    account->gid = -1;
    group->user[AccountGroupFindUserById(group, account->uid)] = -1;

    group->count--; // user count --
    MutexlockIsUnLock(&group->lock);
    return 0;
}

int AccountGroupFindUserById(account_group_t *group, uid_t uid)
{
    int i;

    for (i = 0; i < GROUP_USER_NUM; i++)
    {
        if (group->user[i] == uid)
            return i;
    }
    return -1;
}

int SysCreateGroup(char *name, int attr)
{
    if (SafetyCheckRange(name, strlen(name)) < 0)
        return -EINVAL;

    return AccountGroupRegister(attr, name);
}

int SysRemoveGroup(char *name)
{
    if (SafetyCheckRange(name, strlen(name)) < 0)
        return -EINVAL;

    return AccountGroupUnRegister(name);
}

int SysGroupAddUser(char *name, char *user)
{
    if (SafetyCheckRange(name, strlen(name)) < 0)
        return -EINVAL;
    if (SafetyCheckRange(name, strlen(name)) < 0)
        return -EINVAL;
    return AccountGroupAddUser(user, name);
}

int SysGroupDelUser(char *name, char *user)
{
    if (SafetyCheckRange(name, strlen(name)) < 0)
        return -EINVAL;
    if (SafetyCheckRange(user, strlen(user)) < 0)
        return -EINVAL;
    return AccountGroupDelUser(user, name);
}
